#include "logger.h"

#include "file.h"
#include "file_path.h"
#if ENABLE_ZLIB
#include "gzstream.h"
#endif
#include <stdarg.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>

#include <algorithm>
#include <iostream>

#include "thread_pool.h"
#include "time_ticker.h"
#include "time_util.h"

#define CLEAR_COLOR "\033[0m"
static const char* LOG_CONST_TABLE[][3] = {{"\033[44;37m", "\033[34m", "T"},
                                           {"\033[42;37m", "\033[32m", "D"},
                                           {"\033[46;37m", "\033[36m", "I"},
                                           {"\033[43;37m", "\033[33m", "W"},
                                           {"\033[41;37m", "\033[31m", "E"}};

Logger* g_defaultLogger = nullptr;

Logger& getLogger() {
    if (!g_defaultLogger) {
        g_defaultLogger = &Logger::Instance();
    }
    return *g_defaultLogger;
}

void setLogger(Logger* logger) { g_defaultLogger = logger; }

///////////////////Logger///////////////////

INSTANCE_IMP(Logger, exeName())

Logger::Logger(const std::string& loggerName) {
    _logger_name = loggerName;
    _last_log = std::make_shared<LogContext>();
}

Logger::~Logger() {
    _writer.reset();
    {
        LogContextCapture(*this, LInfo, __FILE__, __FUNCTION__, __LINE__);
    }
    _channels.clear();

    Ticker tick(1000, "logger_exit");
    while (1) {
        auto is_busy = _busy.test_and_set();
        if (!is_busy || tick.elapsedTime() >= 1000) {
            break;
        }

        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

void Logger::add(const std::shared_ptr<LogChannel>& channel) {
    _channels[channel->name()] = channel;
}

void Logger::del(const std::string& name) { _channels.erase(name); }

std::shared_ptr<LogChannel> Logger::get(const std::string& name) {
    auto it = _channels.find(name);
    if (it == _channels.end()) {
        return nullptr;
    }
    return it->second;
}

void Logger::setWriter(const std::shared_ptr<LogWriter>& writer) {
    _writer = writer;
}

void Logger::write(const LogContextPtr& ctx) {
    if (_writer) {
        _writer->write(ctx, *this);
    } else {
        writeChannels(ctx);
    }
}

void Logger::setLevel(LogLevel level) {
    for (auto& chn : _channels) {
        chn.second->setLevel(level);
    }
}

void Logger::writeChannels_l(const LogContextPtr& ctx) {
    for (auto& chn : _channels) {
        chn.second->write(*this, ctx);
    }

    auto is_busy = _busy.test_and_set();
    if (!is_busy) {
        _last_log = ctx;
        _last_log->_repeat = 0;
        _busy.clear();
    }
}

// 返回毫秒
static int64_t timevalDiff(struct timeval& a, struct timeval& b) {
    return (1000 * (b.tv_sec - a.tv_sec)) + ((b.tv_usec - a.tv_usec) / 1000);
}

void Logger::writeChannels(const LogContextPtr& ctx) {
    if (ctx->_line == _last_log->_line && ctx->_file == _last_log->_file &&
        ctx->str() == _last_log->str()) {
        // 重复的日志每隔500ms打印一次，过滤频繁的重复日志
        ++_last_log->_repeat;
        if (timevalDiff(_last_log->_tv, ctx->_tv) > 500) {
            ctx->_repeat = _last_log->_repeat;
            writeChannels_l(ctx);
        }
        return;
    }
    if (_last_log->_repeat) {
        writeChannels_l(_last_log);
    }
    writeChannels_l(ctx);
}

const std::string& Logger::getName() const { return _logger_name; }

///////////////////LogContext///////////////////
static inline const char* getFileName(const char* file) {
    auto pos = strrchr(file, '/');
    return pos ? pos + 1 : file;
}

static inline const char* getFunctionName(const char* func) { return func; }

LogContext::LogContext(LogLevel level,
                       const char* file,
                       const char* function,
                       int line,
                       const char* module_name,
                       const char* flag)
    : _level(level),
      _line(line),
      _file(getFileName(file)),
      _function(getFunctionName(function)),
      _module_name(module_name),
      _flag(flag) {
    gettimeofday(&_tv, nullptr);
    _thread_name = getThreadName();
}

const std::string& LogContext::str() {
    if (_got_content) {
        return _content;
    }
    _content = std::ostringstream::str();
    _got_content = true;
    return _content;
}

///////////////////AsyncLogWriter///////////////////

static std::string s_module_name = exeName(false);

LogContextCapture::LogContextCapture(Logger& logger,
                                     LogLevel level,
                                     const char* file,
                                     const char* function,
                                     int line,
                                     const char* flag)
    : _ctx(new LogContext(
          level, file, function, line, s_module_name.c_str(), flag)),
      _logger(logger) {}

LogContextCapture::LogContextCapture(const LogContextCapture& that)
    : _ctx(that._ctx), _logger(that._logger) {
    const_cast<LogContextPtr&>(that._ctx).reset();
}

LogContextCapture::~LogContextCapture() { *this << std::endl; }

LogContextCapture& LogContextCapture::operator<<(
    std::ostream& (*f)(std::ostream&)) {
    DG_UNUSED(f);

    if (!_ctx) {
        return *this;
    }
    _logger.write(_ctx);
    _ctx.reset();
    return *this;
}

void LogContextCapture::clear() { _ctx.reset(); }

///////////////////AsyncLogWriter///////////////////

AsyncLogWriter::AsyncLogWriter() : _exit_flag(false) {
    _thread = std::make_shared<std::thread>([this]() { this->run(); });
}

AsyncLogWriter::~AsyncLogWriter() {
    _exit_flag = true;
    _sem.post();
    _thread->join();
    flushAll();
}

void AsyncLogWriter::write(const LogContextPtr& ctx, Logger& logger) {
    {
        std::lock_guard<std::mutex> lock(_mutex);
        _pending.emplace_back(std::make_pair(ctx, &logger));
    }
    _sem.post();
}

void AsyncLogWriter::run() {
    setThreadName("async log");
    while (!_exit_flag) {
        _sem.wait();
        flushAll();
    }
}

void AsyncLogWriter::flushAll() {
    decltype(_pending) tmp;
    {
        std::lock_guard<std::mutex> lock(_mutex);
        tmp.swap(_pending);
    }

    tmp.for_each([&](std::pair<LogContextPtr, Logger*>& pr) {
        pr.second->writeChannels(pr.first);
    });
}

///////////////////ConsoleChannel///////////////////

ConsoleChannel::ConsoleChannel(const std::string& name, LogLevel level)
    : LogChannel(name, level) {}

void ConsoleChannel::write(const Logger& logger, const LogContextPtr& ctx) {
    if (_level > ctx->_level) {
        return;
    }

    // linux/windows日志启用颜色并显示日志详情
    format(logger, std::cout, ctx);
}

///////////////////LogChannel///////////////////
LogChannel::LogChannel(const std::string& name, LogLevel level)
    : _name(name), _level(level) {}

LogChannel::~LogChannel() {}

const std::string& LogChannel::name() const { return _name; }

void LogChannel::setLevel(LogLevel level) { _level = level; }

std::string LogChannel::printTime(const timeval& tv) {
    auto tm = getLocalTime(tv.tv_sec);
    char buf[128] = {0};
    snprintf(buf,
             sizeof(buf),
             "%d-%02d-%02d %02d:%02d:%02d.%03d",
             1900 + tm.tm_year,
             1 + tm.tm_mon,
             tm.tm_mday,
             tm.tm_hour,
             tm.tm_min,
             tm.tm_sec,
             (int)(tv.tv_usec / 1000));
    return buf;
}

void LogChannel::format(const Logger& logger,
                        std::ostream& ost,
                        const LogContextPtr& ctx,
                        bool enable_color,
                        bool enable_detail) {
    if (!enable_detail && ctx->str().empty()) {
        // 没有任何信息打印
        return;
    }

    if (enable_color) {
        ost << LOG_CONST_TABLE[ctx->_level][1];
    }

    ost << printTime(ctx->_tv) << " " << LOG_CONST_TABLE[ctx->_level][2] << " ";

    if (enable_detail) {
        ost << (!ctx->_flag.empty() ? ctx->_flag : logger.getName()) << "["
            << getpid() << "-" << ctx->_thread_name;
        ost << "] " << ctx->_file << ":" << ctx->_line << " " << ctx->_function
            << " | ";
    }

    ost << ctx->str();

    if (enable_color) {
        ost << CLEAR_COLOR;
    }

    if (ctx->_repeat > 1) {
        ost << "\r\n    Last message repeated " << ctx->_repeat << " times";
    }
    ost << std::endl;
}

///////////////////FileChannelBase///////////////////

FileChannelBase::FileChannelBase(const std::string& name,
                                 const std::string& path,
                                 LogLevel level)
    : LogChannel(name, level), _path(path) {}

FileChannelBase::~FileChannelBase() { close(); }

void FileChannelBase::write(const Logger& logger,
                            const std::shared_ptr<LogContext>& ctx) {
    if (_level > ctx->_level) {
        return;
    }
    if (!_fstream.is_open()) {
        open();
    }
    // 打印至文件，不启用颜色
    format(logger, _fstream, ctx, false);
}

bool FileChannelBase::setPath(const std::string& path) {
    _path = path;
    return open();
}

const std::string& FileChannelBase::path() const { return _path; }

bool FileChannelBase::open() {
    // Ensure a path was set
    if (_path.empty()) {
        throw std::runtime_error("Log file path must be set.");
    }
    // Open the file stream
    _fstream.close();

    // 创建文件夹
    File::create_path(_path.data(), S_IRWXO | S_IRWXG | S_IRWXU);
    _fstream.open(_path.data(), std::ios::out | std::ios::app);
    if (!_fstream.is_open()) {
        return false;
    }
    // 打开文件成功
    return true;
}

void FileChannelBase::close() { _fstream.close(); }

size_t FileChannelBase::size() { return (_fstream << std::flush).tellp(); }

///////////////////FileChannel///////////////////

static const auto s_second_per_day = 24 * 60 * 60;

// 根据GMT UNIX时间戳生产日志文件名
static std::string getLogFilePath(const std::string& dir, time_t second) {
    auto tm = getLocalTime(second);
    char buf[64] = {0};
    snprintf(buf,
             sizeof(buf),
             "%d-%02d-%02d-%02d-%02d-%02d.log",
             1900 + tm.tm_year,
             1 + tm.tm_mon,
             tm.tm_mday,
             tm.tm_hour,
             tm.tm_min,
             tm.tm_sec);
    return dir + buf;
}

static std::string getLinkFilePath(const std::string& dir,
                                   const std::string& name) {
    char buf[64] = {0};
    snprintf(buf, sizeof(buf), "%s", name.c_str());
    return dir + buf;
}

// 根据日志文件名返回GMT UNIX时间戳
static time_t getLogFileTime(const std::string& full_path) {
    auto name = getFileName(full_path.data());
    struct tm tm;
    memset(&tm, 0, sizeof(tm));
    if (!strptime(name, "%Y-%m-%d-%H-%M-%S", &tm)) {
        return 0;
    }
    // 此函数会把本地时间转换成GMT时间戳
    return mktime(&tm);
}

// 获取1970年以来的第几天
static uint64_t getDay(time_t second) {
    long gmtoff = getGMTOff();
    return (second + gmtoff) / s_second_per_day;
}
// 获取当前是几日
static int64_t getDay2(time_t second) {
    struct tm tm;
    memset(&tm, 0, sizeof(tm));
    tm = getLocalTime(second);
    return tm.tm_mday;
}

static std::string getTargetFile(const std::string& file,
                                 const std::string& dir_prefix) {
    char buf[PATH_MAX] = {0};
    std::string target;
    int ret = readlink(file.c_str(), buf, PATH_MAX);
    if (ret < 0 || ret >= PATH_MAX) {
        ErrorL << "readlink failed, link_file: " << file;
    } else {
        target = std::string(buf, ret);
    }

    auto dir = Dirname(target);
    if (dir.empty()) {
        target = JoinPath(dir_prefix, target);
    }

    return target;
}

FileChannel::FileChannel(const std::string& name,
                         const std::string& dir,
                         LogLevel level)
    : FileChannelBase(name, "", level), _name(name) {
    _last_day = -1;
    _dir = dir;
    if (_dir.back() != '/') {
        _dir.append("/");
    }

    std::string target;
    auto link_file = getLinkFilePath(_dir, _name);
    if (File::fileExist(link_file.c_str())) {
        target = getTargetFile(link_file, _dir);
        if (target.empty() || !File::fileExist(target.c_str())) {
            File::delete_file(link_file.c_str());
        } else {
            _can_write = setPath(target);
            if (!_can_write) {
                ErrorL << "Failed to open log file: " << target;
            } else {
                auto t = mktime_from_string(Basename(target));
                _last_day = getDay2(t);
            }
        }
    } else {
        File::delete_file(link_file.c_str());
    }

#if ENABLE_ZLIB
    std::vector<std::string> compress_list;
    File::scanDir(
        _dir,
        [this, &target, &compress_list](const std::string& path,
                                        bool isDir) -> bool {
            if (!isDir && end_with(path, ".log") &&
                (path.compare(target) != 0)) {
                compress_list.emplace_back(path);
            }
            return true;
        },
        false);
    for (auto& path : compress_list) {
        compress(path);
    }
    compress_list.clear();
#endif

    // 收集所有日志文件
    File::scanDir(
        _dir,
        [this, &link_file](const std::string& path, bool isDir) -> bool {
#if ENABLE_ZLIB
            if (!isDir && end_with(path, ".log.gz")) {
#else
            if (!isDir && end_with(path, ".log")) {
#endif
                _log_file_queue.push_back(path);
            }
            return true;
        },
        false);

    std::sort(_log_file_queue.begin(),
              _log_file_queue.end(),
              std::less<std::string>());
}

void FileChannel::write(const Logger& logger, const LogContextPtr& ctx) {
    // GMT UNIX时间戳
    time_t second = ctx->_tv.tv_sec;
    // 这条日志所在第几天
    auto day = getDay2(second);
    if ((int64_t)day != _last_day) {
        // if (_last_day != -1) {
        //     //重置日志index
        //     _index = 0;
        // }
        // 这条日志是新的一天，记录这一天
        WarnL << "[changeFile] day: " << day << ", _last_day: " << _last_day;
        _last_day = day;
        // 获取日志当天对应的文件，每天可能有多个日志切片文件
        changeFile(second);
    } else {
        // 检查该天日志是否需要重新分片
        checkSize(second);
    }

    // 写日志
    if (_can_write) {
        FileChannelBase::write(logger, ctx);
    } else {
        WarnL << "[changeFile]";
        changeFile(second);
    }
}

void FileChannel::clean() {
    // 获取今天是第几天
    auto today = getDay(time(nullptr));
    // 遍历所有日志文件，删除超过若干天前的过期日志文件
    for (auto it = _log_file_queue.begin(); it != _log_file_queue.end();) {
        auto day = getDay(getLogFileTime(it->data()));
        if (today < day + _log_max_day) {
            // 这个日志文件距今尚未超过一定天数,后面的文件更新，所以停止遍历
            break;
        }
        // 这个文件距今超过了一定天数，则删除文件
        File::delete_file(it->data());
        // 删除这条记录
        it = _log_file_queue.erase(it);
    }

    // 按文件个数清理，限制最大文件切片个数
    while (_log_file_queue.size() > _log_max_count) {
        auto it = _log_file_queue.begin();
        if (*it == path()) {
            // 当前文件，停止删除
            break;
        }
        // 删除文件
        File::delete_file(it->data());
        // 删除这条记录
        _log_file_queue.erase(it);
    }
}

void FileChannel::checkSize(time_t second) {
    // 每60秒检查一下文件大小，防止频繁flush日志文件
    if (second - _last_check_time > 10) {
        size_t size = FileChannelBase::size();
        if (size > _log_max_size * 1024 * 1024) {
            ErrorL << "----------------[changeFile] second: " << second
                   << ", _last_check_time: " << _last_check_time
                   << ", size: " << size;
            changeFile(second);
        }
        _last_check_time = second;
    }
}

void FileChannel::changeFile(time_t second) {
    auto link_file = getLinkFilePath(_dir, _name);
    auto log_file = getLogFilePath(_dir, second);
    bool should_compress = false;
    std::string compress_file;
    auto cur_file = path();

    if (!cur_file.empty()) {
        if (cur_file.compare(log_file) == 0) {
            return;
        }

        // 关闭当前文件，并进行压缩
        should_compress = true;
        compress_file = cur_file;
        close();
        unlink(link_file.c_str());
    } else {
        if (File::fileExist(link_file.c_str())) {
            auto target_file = getTargetFile(link_file, _dir);
            if (target_file.empty()) {
                File::delete_file(link_file.c_str());
            } else {
                should_compress = true;
                compress_file = target_file;
                unlink(link_file.c_str());
            }
        }
    }

    // 打开新的日志文件
    _can_write = setPath(log_file);
    if (!_can_write) {
        ErrorL << "Failed to open log file: " << log_file;
    } else {
        auto ret = symlink(log_file.c_str(), link_file.c_str());
        assert(ret == 0);
    }

    _tp.enqueue(
        [this](bool sc, std::string& path) {
            if (sc) {
#if ENABLE_ZLIB
                // 压缩日志文件
                auto cf = compress(path);
                _log_file_queue.push_back(cf);
#else
                _log_file_queue.push_back(path);
#endif
            }

            // 尝试删除过期的日志文件
            clean();
        },
        should_compress,
        compress_file);
}

#if ENABLE_ZLIB
std::string FileChannel::compress(const std::string& path) {
    std::ifstream compress_in(path);
    std::string target = path + ".gz";
    hogzstr::ogzstream compress_out(target.c_str());

    std::string line;
    while (getline(compress_in, line)) {
        compress_out << line << std::endl;
    }

    compress_in.close();
    compress_out.close();
    File::delete_file(path.c_str());

    return target;
}
#endif

void FileChannel::setMaxDay(size_t max_day) {
    _log_max_day = max_day > 1 ? max_day : 1;
}

void FileChannel::setFileMaxSize(size_t max_size) {
    _log_max_size = max_size > 1 ? max_size : 1;
}

void FileChannel::setFileMaxCount(size_t max_count) {
    _log_max_count = max_count > 1 ? max_count : 1;
}

//////////////////////////////////////////////////////////////////////////////////////////////////

void LoggerWrapper::printLogV(Logger& logger,
                              int level,
                              const char* file,
                              const char* function,
                              int line,
                              const char* fmt,
                              va_list ap) {
    LogContextCapture info(logger, (LogLevel)level, file, function, line);
    char* str = nullptr;
    if (vasprintf(&str, fmt, ap) > 0 && str) {
        info << str;
        free(str);
    }
}

void LoggerWrapper::printLog(Logger& logger,
                             int level,
                             const char* file,
                             const char* function,
                             int line,
                             const char* fmt,
                             ...) {
    va_list ap;
    va_start(ap, fmt);
    printLogV(logger, level, file, function, line, fmt, ap);
    va_end(ap);
}